/// <reference types="jquery" />

interface ICarouselLiteOptions {
    animateNavigateDuration?: number;
    animateResetDuration?: number;
    animateRenderDuration?: number;
    data: ICarouselLiteImageData[];
}

interface ICarouselLiteImageData {
    url?: string;
}

(function (window, $, undefined?: any) {
    let CarouselLiteDefaultOptions: ICarouselLiteOptions = {
        animateNavigateDuration: 100,
        animateResetDuration: 50,
        animateRenderDuration: 90,
        data: []
    };

    function randomString(len?: number): string {
        len = len || 32;
        let $chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678';
        let maxPos = $chars.length;
        let str = '';
        for (let i = 0; i < len; i++) {
            str += $chars.charAt(Math.floor(Math.random() * maxPos));
        }
        return str;
    }

    class CarouselLite {
        constructor(
            private $element: JQuery,
            options?: ICarouselLiteOptions
        ) {
            this.options = $.extend({}, CarouselLiteDefaultOptions, options);
            this.blocking = 0;
            this.namespace = randomString(12);
            this.init();
        }

        private $container: JQuery;
        private namespace: string;

        private options: ICarouselLiteOptions
        private index: number;
        private count: number;
        private blocking: number;

        private touchStarted: boolean;
        private touchPreventDefault: boolean;
        private touchIdentifier: number;
        private touchStartPosX: number;
        private touchStartPoxY: number;
        private touchMoveDirection: number; // -1 left | 0 not moved | 1 right 
        private touchImagesCache: JQuery;

        private init() {
            this.index = 0;
            this.count = 0;

            let $e = this.$element;

            let $container = this.$container = $('<div>').addClass('carousellite-container');
            $e.append($container);

            $container.on('touchstart', (e) => this.onTouchStart(e));
            $container.on('touchmove', (e) => this.onTouchMove(e));
            $container.on('touchend', (e) => this.onTouchEnd(e));
            $container.on('touchcancel', (e) => this.onTouchCancel(e));

            let $templates = $e.find('.carousellite-template');
            if ($templates.length > 0) {
                let $template = $templates.first();
                $container.html($template.html());
            }
            let $navbutton1 = $container.find('.carousellite-prev');
            if ($navbutton1.length > 0) {
                $navbutton1.on('click', (e) => this.onClickPrev(e));
            }
            let $navbutton2 = $container.find('.carousellite-next');
            if ($navbutton2.length > 0) {
                $navbutton2.on('click', (e) => this.onClickNext(e));
            }

            if (!(typeof this.options.data === 'undefined')) {
                this.count = this.options.data.length;
            }

            let $img0, $img1, $img2;
            $container.prepend($img2 = $('<div>').addClass('carousellite-image').hide());
            $container.prepend($img1 = $('<div>').addClass('carousellite-image').hide());
            $container.prepend($img0 = $('<div>').addClass('carousellite-image').hide());

            this.updateImage($img1, this.index);
            $img1.css('left', 0);

            if (this.index > 0) {
                this.updateImage($img0, this.index - 1);
                $img0.css('left', '-100%');
            }

            if (this.index < this.count - 1) {
                this.updateImage($img2, this.index + 1);
                $img2.css('left', '100%');
            }

            $img1.show();

            $(window).on('resize.carousellite-' + this.namespace, (e) => this.onParentResize(e));
            $e.on('resize', (e) => this.onParentResize(e));
            $e.resize();

            $e.addClass('carousellite-shown');
        }

        public destroy() {
            let $container = this.$container;
            $container.find('.carousellite-prev').off('click');
            $container.find('.carousellite-next').off('click');
            $container.off('touchstart');
            $container.off('touchmove');
            $container.off('touchend');
            $container.off('touchcancel');
            $container.remove();
            this.$element.off('resize');
            $(window).off('resize.carousellite-' + this.namespace);
        }

        public onParentResize(e: JQueryEventObject) {
            let $container = this.$element.find('.carousellite-container');
            let $navbutton1 = $container.find('.carousellite-prev');
            let $navbutton2 = $container.find('.carousellite-next');
            $navbutton1.css('top', (this.$element.innerHeight() - $navbutton1.height()) / 2);
            $navbutton2.css('top', (this.$element.innerHeight() - $navbutton2.height()) / 2);
        }

        private updateImage($image: JQuery, index: number, force?: boolean) {
            let data = null;
            if (index >= 0 && index < this.count) {
                data = this.options.data[index];
            }
            if (force || typeof $image.data('carousellite-image-filled') === 'undefined') {
                if (data === null || typeof data.url === 'undefined') {
                    $image.css('background-image', '');
                } else {
                    $image.css('background-image', `url("${data.url}")`);
                }
                $image.data('carousellite-image-filled', 1);
            }
        }

        private onTouchStart(e: JQueryEventObject) {
            if (this.touchStarted || this.blocking != 0)
                return;

            let $container = this.$element.find('.carousellite-container');
            if (typeof this.options.data === 'undefined')
                return;

            this.touchImagesCache = $container.find('.carousellite-image');

            let evt = <TouchEvent>e.originalEvent;
            let touches = evt.changedTouches;

            this.touchStarted = true;
            this.touchPreventDefault = false;
            this.touchIdentifier = touches[0].identifier;
            this.touchStartPosX = touches[0].pageX;
            this.touchStartPoxY = touches[0].pageY;
            this.touchMoveDirection = 0;
        }
        private onTouchMove(e: JQueryEventObject) {
            if (!this.touchStarted)
                return;

            let evt = <TouchEvent>e.originalEvent;
            let touches = evt.changedTouches;

            let touch: Touch = null;
            for (var i = 0; i < touches.length; i++) {
                if (touches[i].identifier == this.touchIdentifier) {
                    touch = touches[i];
                    break;
                }
            }
            if (touch == null)
                return;

            if (this.touchPreventDefault) {
                e.preventDefault();
            } else {
                this.touchPreventDefault = true;
            }

            let delta = touch.pageX - this.touchStartPosX;
            let containerWidth = this.$container.width();
            if (delta < 0) {
                // moving to the left
                if (this.touchMoveDirection != -1) {
                    this.touchMoveDirection = -1;
                    this.touchImagesCache.eq(0).css('left', '-100%').hide();
                    this.touchImagesCache.eq(1).show();
                    this.touchImagesCache.eq(2).show();
                }
                //console.log('moving left : ' + delta);
                if (this.index >= this.count - 1 && delta < -(containerWidth / 4))
                    delta = -(containerWidth / 4);
                if (delta < - containerWidth)
                    delta = -containerWidth;
                this.touchImagesCache.eq(1).css('left', delta);
                this.touchImagesCache.eq(2).css('left', containerWidth + delta);
            } else if (delta > 0) {
                // moving to the right
                if (this.touchMoveDirection != 1) {
                    this.touchMoveDirection = 1;
                    this.touchImagesCache.eq(2).css('left', '100%').hide();
                    this.touchImagesCache.eq(0).show();
                    this.touchImagesCache.eq(1).show();
                }
                if (this.index <= 0 && delta > (containerWidth / 4))
                    delta = containerWidth / 4;
                if (delta > containerWidth)
                    delta = containerWidth;
                this.touchImagesCache.eq(1).css('left', delta);
                this.touchImagesCache.eq(0).css('left', delta - containerWidth);
            } else {
                if (this.touchMoveDirection != 0) {
                    this.touchMoveDirection = 0;
                    this.touchImagesCache.eq(0).css('left', '-100%').hide();
                    this.touchImagesCache.eq(1).css('left', 0);
                    this.touchImagesCache.eq(2).css('left', '100%').hide();
                }
            }
        }
        private onTouchEnd(e: JQueryEventObject) {
            if (!this.touchStarted)
                return;

            let evt = <TouchEvent>e.originalEvent;
            let touches = evt.changedTouches;

            let touch: Touch = null;
            for (var i = 0; i < touches.length; i++) {
                if (touches[i].identifier == this.touchIdentifier) {
                    touch = touches[i];
                    break;
                }
            }
            if (touch == null)
                return;

            let delta = touch.pageX - this.touchStartPosX;
            let width = this.$container.width();
            if (delta > (width / 4) || delta < -(width / 4)) {
                if (delta > 0 && this.index > 0) {
                    this.slidePrev();
                    delta = 0;
                } else if (delta < 0 && this.index < this.count - 1) {
                    this.slideNext();
                    delta = 0;
                }
            }

            if (delta != 0) {
                this.animateResetCurrentImage();
            }
            this.touchStarted = false;
            this.touchImagesCache = null;
        }
        private onTouchCancel(e: JQueryEventObject) {
            if (!this.touchStarted)
                return;

            let evt = <TouchEvent>e.originalEvent;
            let touches = evt.changedTouches;

            let touch: Touch = null;
            for (var i = 0; i < touches.length; i++) {
                if (touches[i].identifier == this.touchIdentifier) {
                    touch = touches[i];
                    break;
                }
            }
            if (touch == null)
                return;

            this.animateResetCurrentImage();
            this.touchStarted = false;
            this.touchImagesCache = null;
        }

        private animateResetCurrentImage() {
            let $container = this.$container;
            let $images = $container.find('.carousellite-image');

            let $prev = $images.eq(0);
            let $cur = $images.eq(1);
            let $next = $images.eq(2);
            if (this.touchMoveDirection == -1) {
                this.blocking += 2;
                $cur.animate({ left: '0' }, this.options.animateResetDuration, () => this.blocking--);
                $next.animate({ left: '100%' }, this.options.animateResetDuration, () => this.blocking--);
            } else if (this.touchMoveDirection == 1) {
                this.blocking += 2;
                $cur.animate({ left: '0' }, this.options.animateResetDuration, () => this.blocking--);
                $prev.animate({ left: '-100%' }, this.options.animateResetDuration, () => this.blocking--);
            }
        }

        private slidePrev() {
            this.index--;
            this.animateToPrevious();
            this.internalRender();
        }
        private animateToPrevious(forceUpdate?: boolean) {
            let $container = this.$container;
            let $images = $container.find('.carousellite-image');

            this.blocking++;

            let $cur = $images.eq(1);
            let $prev = $images.eq(0);
            $images.eq(2).remove();
            if (forceUpdate) {
                this.updateImage($prev, this.index, true);
            }
            $prev.show();
            $cur.animate({ left: '100%' }, this.options.animateNavigateDuration);
            $prev.animate({ left: '0' }, this.options.animateNavigateDuration, () => {
                $cur.hide();
                let $ele = $('<div>').addClass('carousellite-image').hide();
                $ele.css('left', '-100%');
                $prev.before($ele);
                this.updateImage($ele, this.index - 1);
                if (forceUpdate) {
                    this.updateImage($cur, this.index + 1, true);
                }
                this.blocking--;
            });
        }

        private slideNext() {
            this.index++;
            this.animateToNext();
            this.internalRender();
        }
        private animateToNext(forceUpdate?: boolean) {
            let $container = this.$container;
            let $images = $container.find('.carousellite-image');

            this.blocking++;

            let $cur = $images.eq(1);
            let $next = $images.eq(2);
            $images.eq(0).remove();
            if (forceUpdate) {
                this.updateImage($next, this.index, true);
            }
            $next.show();
            $cur.animate({ left: '-100%' }, this.options.animateNavigateDuration);
            $next.animate({ left: '0' }, this.options.animateNavigateDuration, () => {
                $cur.hide();
                let $ele = $('<div>').addClass('carousellite-image').hide();
                $ele.css('left', '100%');
                $next.after($ele);
                this.updateImage($ele, this.index + 1);
                if (forceUpdate) {
                    this.updateImage($cur, this.index - 1, true);
                }
                this.blocking--;
            });
        }

        public render() {
            if (this.blocking > 0)
                return;
            this.internalRender();
        }

        private internalRender() {
            let $main = this.$container.find('.carousellite-main').first();
            if ($main.length > 0) {
                this.blocking++;
                let halfAnimateDuration = this.options.animateNavigateDuration / 2;
                $main.fadeOut(halfAnimateDuration, () => {
                    let $event = $.Event('render', {
                        $container: this.$container,
                        index: this.index,
                        count: this.count,
                        item: this.options.data[this.index]
                    });
                    this.$element.trigger($event);
                    if ($event.isDefaultPrevented()) {
                        this.blocking--;
                    } else {
                        $main.fadeIn(halfAnimateDuration, () => {
                            this.blocking--;
                        });
                    }
                });
            } else {
                let $event = $.Event('render', {
                    $container: this.$container,
                    index: this.index,
                    count: this.count,
                    item: this.options.data[this.index]
                });
                this.$element.trigger($event);
            }
        }

        private onClickPrev(e: JQueryEventObject) {
            if (this.blocking > 0) {
                return;
            }

            let $event = $.Event('clickPrev', { index: this.index, count: this.count });
            this.$element.trigger($event);
            if ($event.isDefaultPrevented()) {
                return;
            }

            if (this.index <= 0) {
                return;
            }

            this.slidePrev();
            return;
        }
        private onClickNext(e: JQueryEventObject) {
            if (this.blocking > 0) {
                return;
            }

            let $event = $.Event('clickNext', { index: this.index, count: this.count });
            this.$element.trigger($event);
            if ($event.isDefaultPrevented()) {
                return;
            }

            if (this.index >= this.count - 1) {
                return;
            }

            this.slideNext();
            return;
        }

        private slideTo(index: number) {
            if (index > this.index) {
                this.index = index;
                this.animateToNext(true);
            } else if (index < this.index) {
                this.index = index;
                this.animateToPrevious(true);
            }

            this.internalRender();
        }

        public getOrSetIndex(index?: number): number {
            if (typeof index === 'undefined') {
                return this.index;
            }

            if (index < 0)
                index = 0;
            else if (index > this.count - 1)
                index = this.count - 1

            this.slideTo(index);
        }

        public goToNext(): void {
            if (this.index < this.count) {
                this.getOrSetIndex(this.index + 1);
            }
        }

        public goToPrev(): void {
            if (this.index != 0) {
                this.getOrSetIndex(this.index - 1);
            }
        }


    }

    $.fn.carousellite = function (method?: (string | ICarouselLiteOptions), options?: any) {
        let result: any = null;
        let $ret = this.each(function () {
            let $this = $(this);
            let obj: CarouselLite = $this.data('carousellite-object');
            if (typeof obj === 'undefined') {
                if (typeof method === 'undefined') {
                    $this.data('carousellite-object', new CarouselLite($this));
                } else if (typeof method === 'string') {
                    if (method == 'init')
                        $this.data('carousellite-object', new CarouselLite($this, options));
                    else
                        throw "carousellite has not benn initialized!";
                } else {
                    $this.data('carousellite-object', new CarouselLite($this, method));
                }
            } else if (typeof method === 'string') {
                switch (method) {
                    case 'init':
                        obj.destroy();
                        $this.data('carousellite-object', new CarouselLite($this, options));
                        break;
                    case 'destroy':
                        obj.destroy();
                        $this.data('carousellite-object', undefined);
                        break;
                    case 'render':
                        obj.render();
                        break;
                    case 'index':
                        result = obj.getOrSetIndex(options);
                        break;
                    case 'next':
                        obj.goToNext();
                        break;
                    case 'prev':
                        obj.goToPrev();
                        break;
                    default:
                        throw method + " is not supported by carousellite!";
                }
            } else {
                $this.data('carousellite-object', new CarouselLite($this, method));
            }
        });
        if (typeof result != 'undefined' && result != null)
            return result;
        return $ret;
    }

})(window, jQuery);

